import java.util.ArrayList;
import java.io.*;
import java.net.*;
//implements a multithreaded server
public class Server implements Runnable{
	private DataCenter dc;
	private int memory;
	private int vcpu;
	private int disk;
	private int lan;
	private int id;
	private int initCpu;	// Hold the original number of CPU's
	private ArrayList<VirtualMachine> vmTypeList;
	private ArrayList<VirtualMachine> activeVmList;
	protected boolean isUp;
    protected Thread  runningThread= null;
    protected ServerSocket  serverSocket= null;
    protected ArrayList<Thread> serverThreads;
	
	public Server(DataCenter dc, PhysicalMachine pm, ArrayList<VirtualMachine> vmTypeList, int serverId) {
	    this.dc=dc;
		setmemory(pm.memory);
	    setvcpu(pm.vcpu);
	    setdisk(pm.disk);
	    setlan(pm.lan);
	    this.vmTypeList=vmTypeList;
	    this.activeVmList = new ArrayList<VirtualMachine>();
	    this.serverThreads= new ArrayList<Thread>();
	    this.id = serverId;
	    this.isUp=true;
	    this.initCpu = pm.vcpu;
	    new Thread(this).start();
	}
	
    public void run(){
        synchronized(this){
            this.runningThread = Thread.currentThread();
        }
        openServerSocket();
        System.out.println("Booted up Server: "+this.id+ " in data center "+this.dc.getName());

        while(isUp()){
            Socket clientSocket = null;
            try {
                clientSocket = this.serverSocket.accept();		    		
	                
	    	
	            Thread t=new Thread(
	            		new VMWorker(clientSocket, this.id, vmTypeList));
	            this.serverThreads.add(t);
	            t.start();

            } catch (IOException e) {
                if(!isUp()) {
                    System.out.println("Server "+this.id+" Stopped.") ;
                    return;
                }
                throw new RuntimeException(
                    "Error accepting client connection", e);
            }            
        }
        System.out.println("Server "+this.id+" on datacenter "+this.dc.getName()+" Stopped.") ;
    }
    
    private synchronized boolean isUp() {
        return this.isUp;
    }

	public synchronized void stop(){
        this.isUp = false;
        try {
        	while(!this.serverThreads.isEmpty())
        		this.serverThreads.remove(0).interrupt();
            this.serverSocket.close();
        } catch (IOException e) {
            throw new RuntimeException("Error closing server", e);
        }
    }

    private void openServerSocket() {
        try {
            this.serverSocket = new ServerSocket(this.id);
        } catch (IOException e) {
            throw new RuntimeException("Cannot open port ", e);
        }
    }

	public int launchVM(int vmTypeIx, int id) {
		VirtualMachine VM = new VirtualMachine();
		int error = 0;
	  
		// Check if enough resources exist
		if (vmTypeList.get(vmTypeIx).getvcpu() > this.getvcpu())
			error = -1;	// Not enough CPUs
		if (vmTypeList.get(vmTypeIx).getmemory() > this.getmemory())
			error = -2;	// Not enough memory
		if (vmTypeList.get(vmTypeIx).getdisk() > this.getdisk())
			error = -3;	// Not enough disk
		if (vmTypeList.get(vmTypeIx).getlan() > this.getlan())
			error = -4;	// Not enough lan
		
		if (error == 0) {
			VM.setvcpu(vmTypeList.get(vmTypeIx).getvcpu());
			VM.setmemory(vmTypeList.get(vmTypeIx).getmemory());
			VM.setdisk(vmTypeList.get(vmTypeIx).getdisk());
			VM.setlan(vmTypeList.get(vmTypeIx).getlan());
			vcpu -= VM.getvcpu();
			memory -= VM.getmemory();
			disk -= VM.getdisk();
			lan -= VM.getlan();
			VM.setid(id);
			System.out.println("Launching vm"+id+" on server"+this.id+" on datacenter"+dc.getName());
			activeVmList.add(VM);
			return id;
		}
		else {
			return error;
		}
	}
	
	public int terminateVM(int vmID) {
		int error = -1;	// Default to VM not found
		
		for (int i=0; i<activeVmList.size(); i++){
			if (activeVmList.get(i).getid() == vmID) {
				System.out.println("Terminating vm "+vmID+" on server"+this.id+" on datacenter"+dc.getName());
				vcpu += activeVmList.get(i).getvcpu();
				memory += activeVmList.get(i).getmemory();
				disk += activeVmList.get(i).getdisk();
				lan += activeVmList.get(i).getlan();
				activeVmList.remove(i);
				error = 0;
				break;
			}
		}
		return error;
	}

	public void printAvailableResources() {

		System.out.println("DataCenter: "+this.dc.getName()+", Server: "+this.id+" Memory: "+
				this.getmemory()+" VCpu: "+this.getvcpu()+" Disk: "+this.getdisk()+" " +
				"Lan: "+this.getlan() );
	}
  
	public int getmemory() {
		return this.memory;
	}
	
	public void setmemory(int memory) {
		this.memory=memory;
	}
	
	public int getvcpu() {
		return this.vcpu;
	}
	
	public int getInitCpu() {
		return this.initCpu;
	}
	
	public void setvcpu(int vcpu) {
		this.vcpu=vcpu;
	}
	
	
	public int getdisk() {
		return this.disk;
	}

	public void setdisk(int disk) {
		this.disk=disk;
	}

	public int getlan() {
		return this.lan;
	}
	
	public void setlan(int lan) {
		this.lan=lan;
	}

	public int getid() {
		return this.id;
	}
	

} 
